﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SiGeSIB.GUI.Common.DedaGrid.Utils;

namespace SiGeSIB.GUI.Common.DedaGrid
{
    public partial class UDedaGrid : UserControl
    {
        #region Constructor
        public UDedaGrid()
        {
            InitializeComponent();

            _GroupingGrids = new List<UGroupingGrid>();
            _StateView = EDedaGridStateView.Ungrouped;

			grid.CellMouseClick += new DataGridViewCellMouseEventHandler(grid_CellMouseClick);
			grid.MouseClick += new MouseEventHandler(UDedaGrid_MouseClick);
			grid.RowsAdded += new DataGridViewRowsAddedEventHandler(_OnRowInserted);
			grid.RowsRemoved += new DataGridViewRowsRemovedEventHandler(_OnRowRemoved);
			grid.UserDeletingRow += new DataGridViewRowCancelEventHandler(_OnRowDeleting);
			grid.CellBeginEdit += new DataGridViewCellCancelEventHandler(_OnCellEditting);
			//grid.CellFormatting += new DataGridViewCellFormattingEventHandler(_OnFormattingRow);
        }
        #endregion

        #region Attributes
        private List<UGroupingGrid> _GroupingGrids;
        private EDedaGridStateView _StateView;
        private DataTable _DataTable;

        // Constante que indica o nível atual da grid.
        private const int LEVEL = 0;
        #endregion

        #region Properties
		public ContextMenuStrip DedaContextMenu
		{
			get;
			set;
		}

        [Browsable(false)]
        public int GroupsCount
        {
            get { return _GroupingGrids.Count; }
        }

        [Browsable(false)]
        public List<string> GroupedColumns
        {
            get;
            set;
        }

		[Browsable(false)]
        private int GroupColumnIndex
        {
            get
            {
                int index = -1;
                if (GroupedColumns != null && GroupedColumns.Count > LEVEL)
                    for (int i = 0; i < _DataTable.Columns.Count; i++)
                        if (_DataTable.Columns[i].ColumnName.Equals(GroupedColumns[LEVEL]))
                            index = i;

                return index;
            }
        }

		[Browsable(false)]
		public UGroupingGrid Owner
		{
			get;
			set;
		}
        #endregion

        #region Methods
		#region Utils
		public List<DataRow> GetSelectedRows()
		{
			List<DataRow> drs = new List<DataRow>();

			if (_StateView == EDedaGridStateView.Ungrouped)
			{
				int count = grid.GetCellCount(DataGridViewElementStates.Selected);
				List<int> rowIndexes = new List<int>();
				int rowIndex;
				for (int i = 0; i < count; i++)
				{
					rowIndex = grid.SelectedCells[i].RowIndex;
					if (!rowIndexes.Contains(rowIndex))
					{
						rowIndexes.Add(rowIndex);
						drs.Add(_DataTable.Rows[rowIndex]);
					}
				}
			}
			else
			{
				List<DataRow> selectedChildRows = null;
				// Posteriormente, isso vira um object.
				// O groupingGrid vai guardar o tipo do objeto, para poder resgatar.
				string groupValue = string.Empty;

				foreach (UGroupingGrid grp in panDedaGrid.Controls)
				{
					selectedChildRows = grp.GetSelectedRows();

					if (selectedChildRows != null && selectedChildRows.Count > 0)
					{
						groupValue = grp.Caption;
						break;
					}
				}
				if (selectedChildRows != null && selectedChildRows.Count > 0)
					drs = _ConvertToParentRows(selectedChildRows, groupValue);
			}
			return drs;
		}
		/// <summary>
		/// Resgata a coluna que foi retirada ao ser inserida no grupo com a tabela filha.
		/// </summary>
		private List<DataRow> _ConvertToParentRows(List<DataRow> childRows, string groupValue)
		{
			List<DataRow> parentRows = null;

			if (childRows != null && childRows.Count > 0)
			{
				parentRows = new List<DataRow>();
				DataTable newDt = _CloneDataTable();
				object[] newItemArray = null;
				foreach (DataRow dr in childRows)
				{
					newItemArray = new object[dr.ItemArray.Length + 1];
					int grpColIndex = GroupColumnIndex;
					int drCount = 0;
					for (int i = 0; i < newItemArray.Length; i++)
					{
						if (i == grpColIndex)
							newItemArray[i] = groupValue;
						else
						{
							newItemArray[i] = dr.ItemArray[drCount];
							drCount++;
						}
					}

					newDt.Rows.Add(newItemArray);
				}

				
				foreach(DataRow dr in newDt.Rows)
					parentRows.Add(dr);
			}
			return
				parentRows;
		}

		public DataRow GetSelectedRow()
		{
			List<DataRow> drs = GetSelectedRows();
			
			DataRow dr = null;
			if (drs != null && drs.Count == 1)
				dr = drs[0];

			return dr;
		}

		private DataTable _CloneDataTable()
		{
			DataTable newDT = new DataTable();
			foreach(DataColumn col in _DataTable.Columns)
				newDT.Columns.Add(new DataColumn(col.ColumnName, col.DataType));

			return newDT;
		}
		#endregion

		#region Changing StateView
		private void SetStateView(EDedaGridStateView state, List<string> groupedColumns)
        {
			GroupedColumns = groupedColumns;

			if (_StateView != state)
            {
                _StateView = state;
                _ChangeStateView();
            }
        }
        private void _ChangeStateView()
        {
            switch (_StateView)
            {
                case (EDedaGridStateView.Ungrouped):
                    _SetUngroupedMode();
                    break;
                case (EDedaGridStateView.Grouped):
                    _SetGroupedMode();
                    break;
            }
        }
        private void _SetGroupedMode()
        {
            panDedaGrid.Controls.Clear();
            UGroupingGrid grp = new UGroupingGrid();
			grp.SelectionChanged += new EventHandler(grid_SelectionChanged);
            grp.Dock = DockStyle.Fill;
            _GroupingGrids.Clear();
            _GroupingGrids.Add(grp);
            panDedaGrid.Controls.Add(grp);
        }
        private void _SetUngroupedMode()
        {
            panDedaGrid.Controls.Clear();
            panDedaGrid.Controls.Add(grid);
        }
        #endregion

		#region Creating DedaGrid
		private void _SetExtendedProperties()
		{
            foreach (DataGridViewColumn gridColumn in grid.Columns)
            {
                DataColumn dtCol = _GetDataTableColumn(gridColumn.Name);
                if (dtCol != null)
                { // Seta todas as ExtendedsProperties
                    gridColumn.Visible = (bool)dtCol.ExtendedProperties["Visible"];
                }
            }
		}
		private DataColumn _GetDataTableColumn(string columnName)
		{
            DataColumn col = null;

            if (_DataTable != null)
				foreach (DataColumn column in _DataTable.Columns)
                    if (column.ColumnName.Equals(columnName))
                    {
                        col = column;
                        break;
                    }

			return col;
		}
		private DataGridViewColumn _ConvertToDataGridViewColumn(DataColumn column)
		{
			DataGridViewColumn gridColumn = new DataGridViewColumn();
			DataGridViewCell cell = new DataGridViewTextBoxCell();

			gridColumn.CellTemplate = cell;
			gridColumn.Name = column.ColumnName;
			gridColumn.HeaderText = column.Caption;
			gridColumn.ValueType = column.DataType;
			gridColumn.Visible = (bool)column.ExtendedProperties["Visible"];

			return gridColumn;
		}
		#endregion

        #region DataBinding
		public void SetDataBinding(DataTable dt)
		{
			SetDataBinding(dt, null);
		}
		public void ApplyFilter(string s)
		{
			if (_DataTable != null)
				_DataTable.DefaultView.RowFilter = s;
		}
		public void SetDataBinding(DataTable dt, List<string> groupedColumns)
        {
            if (groupedColumns == null || groupedColumns.Count == 0 || dt.Rows.Count == 0)
				SetStateView(EDedaGridStateView.Ungrouped, null);
			else
				SetStateView(EDedaGridStateView.Grouped, groupedColumns);

			_DataTable = dt;
			_DataTable.DefaultView.ListChanged += new ListChangedEventHandler(DefaultView_ListChanged);

            _FillDataTable();

            _SetExtendedProperties();
        }

        private void _FillDataTable()
        {
            if (_StateView == EDedaGridStateView.Ungrouped)
                _FillUngroupedMode();
            else
                _FillGroupedMode();
        }
        /// <summary>
        /// Método que preenche uma lista de DataTables,
        /// com DTs hierarquizada de acordo com um nível na lista do grupo.
        /// </summary>
		private void _FillGroupedMode()
		{
			#region Criando o próximo nível de agrupamento.
			List<string> newGroupedColumns = null;

			if (GroupedColumns != null && GroupedColumns.Count > 1)
			{
				newGroupedColumns = new List<string>();
				for (int i = 1; i < GroupedColumns.Count; i++)
					newGroupedColumns.Add(GroupedColumns[i]);
			}

			#endregion
			
            Dictionary<string, List<int>> mappedValues = _MapGroups(_DataTable);
			
			this.panDedaGrid.Controls.Clear();
			_GroupingGrids.Clear();
			if (mappedValues.Count > 0)
			{
				foreach (string key in mappedValues.Keys)
				{
					if (mappedValues[key].Count > 0)
					{
						DataTable dtChild = new DataTable();
						#region Nova DataTable
						// Criando colunas
						foreach (DataColumn col in _DataTable.Columns)
							if (!col.ColumnName.Equals(GroupedColumns[LEVEL]))
							{
								DataColumn newCol = new DataColumn(col.ColumnName, col.DataType);
								foreach (var keyProp in col.ExtendedProperties.Keys)
									newCol.ExtendedProperties.Add(keyProp, col.ExtendedProperties[keyProp]);

								dtChild.Columns.Add(newCol);
							}
						// Adicionando as linhas.
						foreach (int index in mappedValues[key])
							dtChild.Rows.Add(_ConvertToChildRow(_DataTable.Rows[index]));
						#endregion

						UGroupingGrid newGroup = new UGroupingGrid();
						#region Inicializando o novo agrupamento.
						newGroup.Anchor = ((AnchorStyles.Top
											| AnchorStyles.Left)
											| AnchorStyles.Right);
						newGroup.Owner = this;
						newGroup.Caption = key;
						#endregion

						newGroup.SetDataBinding(dtChild, newGroupedColumns);
						newGroup.SelectionChanged += new EventHandler(grid_SelectionChanged);
						this.panDedaGrid.Controls.Add(newGroup);
						_GroupingGrids.Add(newGroup);
						newGroup.ClearSelection();

					}
				}
				SetHeight(false);
			}
        }

		private object[] _ConvertToChildRow(DataRow row)
		{
			return
				_ConvertToChildRow(row.ItemArray);
		}
        private object[] _ConvertToChildRow(object[] itemArray)
        {
            object[] childRow = new object[itemArray.Length - 1];

            int indexChildRow = 0;
            for (int i = 0; i < itemArray.Length; i++)
            {
                if (i != GroupColumnIndex)
                {
                    childRow[indexChildRow] = itemArray[i];
                    indexChildRow++;
                }
            }
            return childRow;
        }
        /// <summary>
        /// Método que mapeia a grid em um dicionário no seguinte formato:
        /// chave: string do agrupamento
        /// conteúdo: List(int) preenchida com o número da linha que pertence aquele grupo.
        /// </summary>
        private Dictionary<string, List<int>> _MapGroups(DataTable dt)
        {
            Dictionary<string, List<int>> groups = new Dictionary<string, List<int>>();

            DataRow row;
            for (int i = 0; i < dt.Rows.Count; i++)
            {
                row = dt.Rows[i];
                if (row != null)
                {
                    if (!groups.ContainsKey(row[GroupedColumns[LEVEL]].ToString()))
                        groups.Add(row[GroupedColumns[LEVEL]].ToString(), new List<int>());

                    groups[row[GroupedColumns[LEVEL]].ToString()].Add(i);
                }
            }

            return groups;
        }

        private void _FillUngroupedMode()
        {
			grid.DataSource = _DataTable.DefaultView;

			SetHeight(false);
        }
        #endregion

		#region UIState
		public void SetHeight()
		{
			SetHeight(true);
		}
		public void SetHeight(bool resizeOwner)
		{
			if (_StateView == EDedaGridStateView.Grouped)
			{
				#region Grouped
				Size minimumSize = new Size(0, 0);
				foreach (Control ctrl in panDedaGrid.Controls)
				{
					#region Calculando o tamanho mínimo
					minimumSize.Height += ctrl.Height + 8;
					if (ctrl.Width > minimumSize.Height)
						minimumSize.Width = ctrl.Width;
					#endregion
				}

				this.Size = this.AutoScrollMinSize = minimumSize;
				#endregion
			}
			else
			{
				#region Ungrouped
				grid.Height =
						23 // Tamanho do Header.
						+
						grid.RowTemplate.Height * _DataTable.DefaultView.Count;
				this.Size = this.AutoScrollMinSize = new Size(this.Width, grid.Height + 10); 
				#endregion
			}

			if (resizeOwner && Owner != null)
				Owner.SetHeight();
		}
		#endregion

		#region Selection Changed
		private void grid_Enter(object sender, EventArgs e)
		{
			RequestFocus(this);
		}

		public void RequestFocus(UDedaGrid requester)
		{
			if (Owner != null)
				Owner.OnRequestFocus(requester);
			else
				Unfocus(requester);
		}
		public void Unfocus(UDedaGrid requester)
		{
			if (_StateView == EDedaGridStateView.Grouped)
			{
				foreach (UGroupingGrid grp in panDedaGrid.Controls)
					grp.Unfocus(requester);
			}
			else if (this != requester)
				grid.ClearSelection();
		}

		#region Work-Around To Deselect the firstRow of Grid.
		private bool _IsFirstSelectionChanged = true;
		private void grid_SelectionChanged(object sender, EventArgs e)
		{
            /*
			if (_IsFirstSelectionChanged && Owner != null)
			{
				grid.ClearSelection();
			}
			_IsFirstSelectionChanged = false;
            */
			_OnSelectionChanged();
		} 
		#endregion
		#endregion

		#endregion

		#region EventHandlers
        //public EventHandler<DedaGridFormattingRowEventArgs> FormattingRow;
		//private void _OnFormattingRow(object sender, DataGridViewCellFormattingEventArgs args)
		//{
		//    if (FormattingRow != null)
		//    {
		//        DedaGridFormattingRowEventArgs formArgs = new DedaGridFormattingRowEventArgs(args.RowIndex, args.Value, args.DesiredType, args.CellStyle);
		//        FormattingRow(this, formArgs);
		//        args.FormattingApplied = formArgs.FormattingApplied;
		//    }

		//}
		private void _OnCellEditting(object sender, DataGridViewCellCancelEventArgs args)
		{
		}

        public event EventHandler SelectionChanged;
		private void _OnSelectionChanged()
		{
            if (SelectionChanged != null)
                SelectionChanged(this, new EventArgs());
		}

		private void grid_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
		{
			if (e.RowIndex >= 0)
				grid.Rows[e.RowIndex].Selected = true;
		}

		private void UDedaGrid_MouseClick(object sender, MouseEventArgs e)
		{
			if(e.Button == MouseButtons.Right)
			{
				DataGridView.HitTestInfo info = grid.HitTest(e.X, e.Y);
				if (DedaContextMenu != null && info.Type == DataGridViewHitTestType.Cell)
					DedaContextMenu.Show(this, e.Location);
			}
		}

		public EventHandler<DedaGridRowsInsertedEventArgs> RowInserted;
		private void _OnRowInserted(object sender, DataGridViewRowsAddedEventArgs args)
		{
			if (RowInserted != null)
				RowInserted(this,
					new DedaGridRowsInsertedEventArgs(args.RowIndex, args.RowCount));
		}

		public EventHandler<DedaGridRowCancelEventArgs> RowDeleting;
		private void _OnRowDeleting(object sender, DataGridViewRowCancelEventArgs args)
		{
            if (RowDeleting != null)
            {
                DedaGridRowCancelEventArgs deletingArgs = new DedaGridRowCancelEventArgs(args.Row);
                RowDeleting(this, deletingArgs);
                // TODO: Testar se esse cancelar funciona.
                args.Cancel = deletingArgs.Cancel;
            }
		}

		public event EventHandler<DedaGridRowsRemovedEventArgs> RowsRemoved;
		private void _OnRowRemoved(object sender, DataGridViewRowsRemovedEventArgs args)
		{
			if (RowsRemoved != null)
				RowsRemoved(this, new DedaGridRowsRemovedEventArgs(args.RowIndex, args.RowCount));
		}

		private void DefaultView_ListChanged(object sender, ListChangedEventArgs e)
		{
			if (_StateView == EDedaGridStateView.Grouped)
			{
				foreach (var control in _GroupingGrids)
					control.ApplyFilter(_DataTable.DefaultView.RowFilter);
			}

			SetHeight();
		}
		#endregion

        internal void ClearSelection()
        {
            if (_StateView == EDedaGridStateView.Ungrouped)
                grid.ClearSelection();
        }
    }
}
